#!/usr/bin/python
# -*- coding: utf-8 -*-
'''
This module is developed as a part of Android AutoTest Framework for Android device,
and used as for test reporting purpose.

This module is a public domain software, and you may redistribute it and/or modify
it under the same terms as LGPL2.1 license itself, so long as this copyright message
and disclaimer are retained in their original form.

IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF
THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.

THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE.  THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS,
AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

© 2011 Sunny Lin. All Rights Reserved. 
Created on 2011/11/17
'''
__author__ = "Sunny Lin"
__email__ = "013.lin@gmail.com"
__version__ = "0.13"

import logging
import logging.config
import os
from collections import defaultdict
from aaf.monkeyErrors import *
g_logger=logging.getLogger()

class TrickResult:
  ''' A TrickResult is to log the error or failure of every step '''
  def __init__(self, trick):
    self.trick=trick
    self.step_exceptions={} # data structure { step:excpetion_obj }
    self.no_exception=True

  def __str__(self):
    steps=self.step_exceptions.keys()
    steps.sort()
    string=self.trick.id
    for step in steps :
      string+="%s\n%s" % (self.trick.getStepDescription(step), self.step_exceptions[step])
    return string
  
  def addStepException(self, step, exception): 
    if self.no_exception :
      self.no_exception=False
    self.step_exceptions[step]=exception
   
class TrickRecorder:
  """ A container to collect the MonkeyTrick's exception.
  There are two kind of exception, one is error (inherit from MonkeyError) and another is failure (inherit from MonkeyFailure)
  Failure means the outcome is not as expected. So it is a really case of test fail.
  Error means something happened unexpectedlly that make test can't go on and don't know if test will success or fail.
  """
  def __init__(self):
    self.tricks_result=defaultdict(dict) # data structure { trickid:{round:TrickResult} }
    self.started=False
      
  def start(self, trick):
    "Must be called for recording before the test(MonkeyTrick) is about to be play"
    g_logger.debug("Start recordind trick %s ..." % (trick))
    result_of_rounds={} #self.tricks_result[trick.id]
    result_of_rounds[1]=TrickResult(trick)
    self.tricks_result[trick.id]=result_of_rounds
    self.started=True
      
  def stop(self, test):
    "Called when the test(MonkeyTrick) is done"
    self.started=False
    
  def getRecordedTricks(self):
    ''' Get a list of tricks those is recorded '''
    tricks=[]
    for result in self.tricks_result.values() :
      if(len(result)>0) : # result keep a list of TrickResult of each round
        tricks.append(result.values()[0].trick)
    tricks.reverse()
    return tricks
    
  def recordException(self, trick, at_round, step, exception):
    ''' Called when an exception happened to record for the trick and at what round  '''
    g_logger.debug("recordException:%s, %d, %s, %s" % (trick, at_round, step, exception))
    result_of_rounds=self.tricks_result[trick.id]
    g_logger.debug("get trick(%s) Exception :%s " % (trick.id, result_of_rounds))
    if (result_of_rounds.has_key(at_round) and result_of_rounds[at_round] is not None):
      result_of_rounds[at_round].addStepException(step, exception)
      g_logger.debug("add result_of_rounds[%d]=TrickResult %s" % (at_round, result_of_rounds[at_round]))
    else :
      result_of_rounds[at_round]=TrickResult(trick)
      result_of_rounds[at_round].addStepException(step, exception)
      g_logger.debug("new result_of_rounds[%d]=TrickResult %s" % (at_round, result_of_rounds[at_round]))
    self.tricks_result[trick.id]=result_of_rounds
    
  def getTrickRounds(self, trick):
    return (len(self.tricks_result[trick.id]))

  def getException(self, trick=None, at_round=0):
    ''' if trick is None, then return all trick's exceptions
        else if specify trick, and just return those exception for that trick and for those specified round
        or all of round, if not specified.
    '''
    if(trick is None) :
      return (self.tricks_result)
    elif(trick is not None and at_round<=0) :
      return (self.tricks_result[trick.id])
    else : #elif(trick is not None and at_round>0) :
      return ((self.tricks_result[trick.id])[at_round]) 
  
  def hasException(self, trick=None, at_round=0):
    ''' check if there are exceptions for trick, and/or at round.
    '''
    if(trick is None) :
      return (len(self.tricks_result)==0)
    elif(trick is not None and at_round<=0) :
      return (len(self.tricks_result[trick.id]))
    else : #elif(trick is not None and at_round>0) :
      return (len((self.tricks_result[trick.id])[at_round])>0)

  '''
  def __repr__(self):
    return "<%s run=%i errors=%i failures=%i>" % \
          (self.__class__.__name__, self.runCount, len(self.errors), len(self.failures))

  def _exc_info_to_string(self, err, test):
    """Converts a sys.exc_info()-style tuple of values into a string."""
    exctype, value, tb = err
    # Skip test runner traceback levels
    while tb and self._is_relevant_tb_level(tb):
      tb = tb.tb_next
    if exctype is test.failureException:
      # Skip assert*() traceback levels
      length = self._count_relevant_tb_levels(tb)
      return ''.join(traceback.format_exception(exctype, value, tb, length))
    return ''.join(traceback.format_exception(exctype, value, tb))

  def _is_relevant_tb_level(self, tb):
    return tb.tb_frame.f_globals.has_key('__unittest')

  def _count_relevant_tb_levels(self, tb):
    length = 0
    while tb and not self._is_relevant_tb_level(tb):
      length += 1
      tb = tb.tb_next
    return length
  '''
 
      
class ShowReporter :
  def __init__(self, stopOnError=True, recorder=None):
    path = os.path.dirname(__file__) # D:\monkeyrunner\py\sqa\monkeytest\
    logging.config.fileConfig(path+'\conf\logging.ini')
    # create logger
    #logger = logging.getNamedLogger(__name__)
    self.stopOnError=stopOnError
    self.logger = logging.getLogger("ProgressLogger")
    if(recorder is None) : recorder=TrickRecorder()
    self.recorder = recorder
    
  def getNamedLogger(self, logger_name=None):
    if (logger_name is None) :
      return self.logger
    else :
      return logging.getLogger(logger_name)
   
  def generateReport(self):
    raise MonkeyError("Method 'generateReport()' is not implemented. Use other class that has implement instead.")
  
class ConsoleReporter(ShowReporter):   
  def generateReport(self):
    ''' ToDo: Provide a console output for test result
    '''
    logger=self.getNamedLogger(self.__class__.__name__)
    logger.info("\n[Generating report ....]")
    logger.info(("="*70))
    for trick in self.recorder.getRecordedTricks():
      results=self.recorder.getException(trick)
      roundlist=results.keys()
      roundlist.sort()
      logger.info("%s test result with %d round:" % (trick.id, trick.round_))
      for round_ in roundlist :
        if (results[round_].no_exception) :
          logger.info("Test success.")
        else:
          test_result=results[round_]
          logger.info("Round %d." % (round_))
          for step, exception in test_result.step_exceptions.items() :
            if isinstance(exception, MonkeyFailure): 
              pic_file=("%s(%d)_%s" % (trick.__class__.__name__, round_, step)) 
              trick.my_device.savePicture(exception.fail_pic, pic_file)
              logger.info("%s failed and save snapshot to file:%s" % (step, pic_file))
      logger.info("-"*70)
          
    #if not self.recorder.hasException():
    #  logger.info("All tested with no errors or failures.")
    logger.info(("="*70))

class TextReporter(ShowReporter):
  def generateReport(self):
    ''' ToDo: Provide a formated text file output for test result
    '''
    pass

class CvsReporter(ShowReporter):
  def generateReport(self):
    ''' ToDo: Provide a formated text file output for test result
    '''
    pass

class HtmlReporter(ShowReporter):
  def generateReport(self):
    ''' ToDo: Provide a formated html file output for test result
    '''
    pass
  
class ExcelReporter(ShowReporter):
  def generateReport(self):
    ''' ToDo: Provide a formated excel file output for test result
    '''
    pass 
